﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SanctusSoftware.Music.Chords
{
    public class NoteTransposer
    {

        // Note: Double-Flats and Double-Sharps have to come first to ensure proper matching
        public const string NotePattern = @"[A-G]([#][#]|♯♯|[#]|♯|♭♭|bb|♭|b)?";

        private static NoteCollection notes = null;

        static NoteTransposer()
        {

            notes = new NoteCollection();
            
            //notes.Add(-12, new string[] { "A", "Bbb", "G##" });
            //notes.Add(-11, new string[] { "A#", "Bb" });
            //notes.Add(-10, new string[] { "B", "Cb", "A##" });
            //notes.Add(-9, new string[] { "C", "B#", "Dbb" });
            //notes.Add(-8, new string[] { "C#", "Db" });
            //notes.Add(-7, new string[] { "D", "C##", "Ebb" });
            //notes.Add(-6, new string[] { "D#", "Eb"});
            //notes.Add(-5, new string[] { "E", "Fb", "D##" });
            //notes.Add(-4, new string[] { "F", "E#", "Gbb" });
            //notes.Add(-3, new string[] { "F#", "Gb" });
            //notes.Add(-2, new string[] { "G", "F##", "Abb" });
            //notes.Add(-1, new string[] { "G#", "Ab" });
            notes.Add(0, new string[] { "A", "Bbb", "G##" });
            notes.Add(1, new string[] { "A#", "Bb" });
            notes.Add(2, new string[] { "B", "Cb", "A##" });
            notes.Add(3, new string[] { "C", "B#", "Dbb" });
            notes.Add(4, new string[] { "C#", "Db" });
            notes.Add(5, new string[] { "D", "C##", "Ebb" });
            notes.Add(6, new string[] { "D#", "Eb" });
            notes.Add(7, new string[] { "E", "Fb", "D##" });
            notes.Add(8, new string[] { "F", "E#", "Gbb" });
            notes.Add(9, new string[] { "F#", "Gb" });
            notes.Add(10, new string[] { "G", "F##", "Abb" });
            notes.Add(11, new string[] { "G#", "Ab" });
            notes.Add(12, new string[] { "A", "Bbb", "G##" });

        }

        public static Note GetRelativeNote(string originalNoteName, int distance)
        {

            Note originalNote = notes.Find(delegate(Note item)
            {
                return item.HasNoteName(originalNoteName);
            });

            int relativeNoteIndex = originalNote.Index + distance;

            while (relativeNoteIndex < 0)
                relativeNoteIndex += 12;

            while (relativeNoteIndex > 12)
                relativeNoteIndex -= 12;

            return notes.Find(delegate(Note item)
            {
                return item.Index == relativeNoteIndex;
            });

        }

        public static int GetNoteNameDistance(string originalNoteName, string targetNoteName)
        {

            int originalNoteLetter = (int)originalNoteName.Substring(0, 1).ToUpper().ToCharArray()[0];
            int targetNoteLetter = (int)targetNoteName.Substring(0, 1).ToUpper().ToCharArray()[0];

            if (targetNoteLetter > originalNoteLetter)
            {
                return targetNoteLetter - originalNoteLetter;
            }
            else
            {
                return -(originalNoteLetter - targetNoteLetter);
            }
            

        }

        public static int GetNotePitchDistance(string originalNoteName, string targetNoteName)
        {

            Note originalNote = notes.Find(delegate(Note item)
            {
                return item.HasNoteName(originalNoteName);
            });

            if (originalNote == null)
            {
                throw new ArgumentException("Note " + originalNoteName + " not found");
            }

            Note targetNote = notes.Find(delegate(Note item)
            {
                return item.HasNoteName(targetNoteName);
            });

            if (targetNote == null)
            {
                throw new ArgumentException("Note " + targetNote + " not found");
            }

            if (targetNote.Index > originalNote.Index)
            {
                return targetNote.Index - originalNote.Index;
            }
            else
            {
                return -(originalNote.Index - targetNote.Index);
            }

        }

        public static string GetTransposedNoteName(string noteName, string originalKey, string targetKey)
        {
            int pitchDistance = GetNotePitchDistance(originalKey, targetKey);
            int nameDistance = GetNoteNameDistance(originalKey, targetKey);
            return GetTransposedNoteName(noteName, pitchDistance, nameDistance);
        }

        public static string GetTransposedNoteName(string noteName, int pitchDistance, int nameDistance)
        {

            // Oh, this should be EASY now!

            Note note = notes.Find(delegate(Note item)
            {
                return item.HasNoteName(noteName);
            });

            if (note == null)
            {
                throw new ArgumentException("Note " + noteName + " not found");
            }

            int targetNoteIndex = note.Index + pitchDistance;

            if (targetNoteIndex > 12)
            {
                targetNoteIndex -= 12;
            }

            if (targetNoteIndex < 0)
            {
                targetNoteIndex += 12;
            }

            Note targetNote = notes.Find(delegate(Note item)
            {
                return item.Index == targetNoteIndex;
            });

            if (targetNote == null)
            {
                throw new Exception("Target note could not be determined");
            }

            string noteNameLetter = noteName.Trim().Substring(0, 1).ToUpper();
            int noteNameCode = (int)noteNameLetter.ToCharArray()[0];

            int targetNoteNameCode = noteNameCode + nameDistance;
            if (targetNoteNameCode < (int)'A')
            {
                targetNoteNameCode += 7;
            }
            else if (targetNoteNameCode > (int)'G')
            {
                targetNoteNameCode -= 7;
            }

            string targetNoteNameLetter = ((char)targetNoteNameCode).ToString();


            return targetNote.GetNoteNameFromNoteLetter(targetNoteNameLetter);



        }


    }
}
